use rand::rngs::{StdRng, ThreadRng};
use rand::{Rng, SeedableRng};
use rstest::rstest;

use super::super::*;
use crate::tests::common::assert_expected_result_or_error;
use crate::tests::fixtures::{rand_float, rand_usize, RANDOM_USIZE};

/// A simple function that uses RngCoreExt to demonstrate usability
fn generate_random_number<R: RngCoreExt>(rng: &mut R) -> i32 {
    rng.random_range(0..10)
}

#[test]
fn test_generate_random_number_with_rng() {
    let mut rng: ThreadRng = rand::rng();
    let number = generate_random_number(&mut rng);
    // We can assert a specific range because we know the function's behavior
    assert!(
        (0..10).contains(&number),
        "The generated number should be within the expected range."
    );
}

#[test]
fn test_generate_random_number_with_std_rng() {
    let seed = [0u8; 32]; // Fixed seed for reproducibility
    let mut rng: StdRng = SeedableRng::from_seed(seed);
    let number = generate_random_number(&mut rng);
    // We can assert a specific range because we know the function's behavior
    assert_eq!(
        number, 4,
        "The generated number should be within the expected range."
    );
}

#[rstest]
#[case::zero_tiles(
    vec![],
    vec![],
)]
#[case::one_tile(
    vec![rand_usize(8, 23)],
    vec![rand_float()],
)]
#[case::four_tiles(
    vec![
        rand_usize(0, 3),
        rand_usize(0, 7),
        rand_usize(0, 9),
        rand_usize(0, 7),
    ],
    vec![
        rand_float(),
        rand_float(),
        rand_float(),
        rand_float(),
    ],
)]
fn test_tile_connection_raw_initialization(#[case] tiles: Vec<usize>, #[case] weights: Vec<f64>) {
    let tile_connection = TileConnection {
        tiles: tiles.clone(),
        weights: weights.clone(),
    };
    assert_eq!(tile_connection.tiles, tiles);
    assert_eq!(tile_connection.weights, weights);
}

#[test]
fn test_tile_connection_default() {
    let tile_connection = TileConnection::default();
    assert_eq!(tile_connection.tiles, Vec::<usize>::new());
    assert_eq!(tile_connection.weights, Vec::<f64>::new());
}

#[rstest]
#[should_panic(expected = "index out of bounds: the len is 0 but the index is 0")]
#[case::zero_tiles(
    vec![],
    vec![],
    0,
)]
#[case::one_tile(
    vec![*RANDOM_USIZE],
    vec![rand_float()],
    *RANDOM_USIZE,
)]
#[case::first_of_three_tiles(
    vec![
        *RANDOM_USIZE,
        rand_usize(0, 2345),
        rand_usize(2500, 4000),
    ],
    vec![
        rand_float() * 803.2,
        0.0,
        -4.2,
    ],
    *RANDOM_USIZE,
)]
#[case::third_of_four_tiles(
    vec![
        rand_usize(0, 9),
        rand_usize(10, 20),
        *RANDOM_USIZE + 6,
        rand_usize(30, 40),
    ],
    vec![
        rand_float(),
        rand_float(),
        3.2,
        rand_float(),
    ],
    *RANDOM_USIZE + 6,
)]
#[case::last_of_five_tiles(
    vec![4, 4, 5, 2, 3],
    vec![1.2, 1.2, 2.2, 1.11, 2.21],
    3,
)]
fn test_select_tile_by_priority(
    #[case] tiles: Vec<usize>,
    #[case] weights: Vec<f64>,
    #[case] expected_tile_id: usize,
) {
    let tile_connection = TileConnection { tiles, weights };
    let tile_id: usize = tile_connection.select_tile_by_priority();

    assert_eq!(tile_id, expected_tile_id);
}

#[rstest]
#[case::zero_tiles(
    vec![],
    vec![],
    None,
    Some(Error::ValueError(
        "selecting a tile randomly failed: Weights sequence is empty/too long/unordered".to_string()
    )),
)]
#[case::one_tile(
    vec![*RANDOM_USIZE],
    vec![rand_float()],
    Some(*RANDOM_USIZE),
    None,
)]
#[case::second_of_three_tiles(
    vec![
        rand_usize(0, 3),
        *RANDOM_USIZE,
        rand_usize(2500, 4000),
    ],
    vec![
        0.2,
        0.12,
        0.0,
    ],
    Some(*RANDOM_USIZE),
    None,
)]
#[case::one_weight_lower_than_zero(
    vec![
        rand_usize(0, 3),
        rand_usize(2500, 4000),
        *RANDOM_USIZE,
    ],
    vec![
        0.2,
        -0.02,
        0.0,
    ],
    None,
    Some(Error::ValueError(
        "selecting a tile randomly failed: A weight is negative, too large or not a valid number"
        .to_string()
    )),
)]
#[case::one_weight_lower_than_zero(
    vec![
        rand_usize(0, 3),
        rand_usize(2500, 4000),
    ],
    vec![
        0.0,
        0.0,
    ],
    None,
    Some(Error::ValueError(
        "selecting a tile randomly failed: Not enough weights > zero".to_string()
    )),
)]
fn test_select_tile_randomly(
    #[case] tiles: Vec<usize>,
    #[case] weights: Vec<f64>,
    #[case] expected_tile_id: Option<usize>,
    #[case] expected_error: Option<Error>,
) {
    let seed = [2u8; 32]; // A fixed seed ensures determinism
    let mut rng = StdRng::from_seed(seed);
    let tile_connection = TileConnection { tiles, weights };
    let result = tile_connection.select_tile_randomly(&mut rng);

    assert_expected_result_or_error(result, expected_tile_id, expected_error);
}
